---
title:  玩转d3.js
author: ajn404
pubDatetime: 2023-09-04T13:06:33Z
postSlug: d3
featured: false
draft: false
tags:
  - npm
  - d3
  - svg
  - canvas
description:
  "d3,数据可视化工具"
---


import Tree from '@components/react/d3/tree'
import Common from '@components/react/doodle/common.tsx';


<Tree client:load/>


## 目录



## 简介

<fieldset>

  <legend>什么是D3</legend>

    <blockquote cite="https://d3js.org/what-is-d3">
  <p> D3 (or D3.js) is a free, open-source JavaScript library for visualizing data. Its low-level approach built on web standards offers unparalleled flexibility in authoring dynamic, data-driven graphics. For more than a decade D3 has powered groundbreaking and award-winning visualizations, become a foundational building block of higher-level chart libraries, and fostered a vibrant community of data practitioners around the world.</p>
  <footer><cite>https://d3js.org/what-is-d3</cite></footer>
</blockquote>


</fieldset>


## getting started

```shell
npm install d3
```

```html
<!DOCTYPE html>
<div id="container"></div>
<script type="module">

import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";

// Declare the chart dimensions and margins.
const width = 640;
const height = 400;
const marginTop = 20;
const marginRight = 20;
const marginBottom = 30;
const marginLeft = 40;

// Declare the x (horizontal position) scale.
const x = d3.scaleUtc()
    .domain([new Date("2023-01-01"), new Date("2024-01-01")])
    .range([marginLeft, width - marginRight]);

// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
    .domain([0, 100])
    .range([height - marginBottom, marginTop]);

// Create the SVG container.
const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height);

// Add the x-axis.
svg.append("g")
    .attr("transform", `translate(0,${height - marginBottom})`)
    .call(d3.axisBottom(x));

// Add the y-axis.
svg.append("g")
    .attr("transform", `translate(${marginLeft},0)`)
    .call(d3.axisLeft(y));

// Append the SVG element.
container.append(svg.node());

</script>
```

## demo

### tree组件

```mdx
import Tree from '@components/react/d3/tree'

<Tree client:load/>
```

### [齿轮组件](https://observablehq.com/d/f4715544ebbb94fd)

import Gear from '@components/react/d3/gear'

<Gear client:load/>

<details>
<summary>tsx component</summary>

```tsx
import * as d3 from "d3";
import { useEffect, useRef } from "react"


const Gear = () => {
    const ref = useRef<SVGSVGElement | null>(null);

    function gear({ teeth, radius, annulus, toothRadius, holeRadius }: any) {
        const n = teeth;
        let r2 = Math.abs(radius);
        let r0 = r2 - toothRadius;
        let r1 = r2 + toothRadius;
        let r3 = holeRadius;
        if (annulus) r3 = r0, r0 = r1, r1 = r3, r3 = r2 + toothRadius * 3;
        const da = Math.PI / n;
        let a0 = -Math.PI / 2 + (annulus ? Math.PI / n : 0);
        const path = ["M", r0 * Math.cos(a0), ",", r0 * Math.sin(a0)];
        let i = -1;
        while (++i < n) {
            path.push(
                "A", r0, ",", r0, " 0 0,1 ", r0 * Math.cos(a0 += da), ",", r0 * Math.sin(a0),
                "L", r2 * Math.cos(a0), ",", r2 * Math.sin(a0),
                "L", r1 * Math.cos(a0 += da / 3), ",", r1 * Math.sin(a0),
                "A", r1, ",", r1, " 0 0,1 ", r1 * Math.cos(a0 += da / 3), ",", r1 * Math.sin(a0),
                "L", r2 * Math.cos(a0 += da / 3), ",", r2 * Math.sin(a0),
                "L", r0 * Math.cos(a0), ",", r0 * Math.sin(a0)
            );
        }
        path.push("M0,", -r3, "A", r3, ",", r3, " 0 0,0 0,", r3, "A", r3, ",", r3, " 0 0,0 0,", -r3, "Z");
        return path.join("");
    }

    const graphic = () => {

        const x = Math.sin(2 * Math.PI / 3);
        const y = Math.cos(2 * Math.PI / 3);
        const svgElement = d3.select(ref.current);

        const svg = svgElement
            .attr("width", 640)
            .attr("viewBox", [-0.53, -0.53, 1.06, 1.06])
            .attr("stroke", "black")
            .attr("stroke-width", 1 / 640)
            .attr("style", `width:${100}vw;max-width: 100%; height: calc( 100vh - 100px);`);

        const frame = svg.append("g");

        const path = frame.selectAll()
            .data([
                { fill: "#c6dbef", teeth: 80, radius: -0.5, origin: [0, 0], annulus: true },
                { fill: "#6baed6", teeth: 16, radius: +0.1, origin: [0, 0] },
                { fill: "#9ecae1", teeth: 32, radius: -0.2, origin: [0, -0.3] },
                { fill: "#9ecae1", teeth: 32, radius: -0.2, origin: [-0.3 * x, -0.3 * y] },
                { fill: "#9ecae1", teeth: 32, radius: -0.2, origin: [0.3 * x, -0.3 * y] }
            ])
            .join("path")
            .attr("fill", d => d.fill)
            .attr("d", d => gear({ ...d, toothRadius: 0.008, holeRadius: 0.02 }));

        Object.assign(svg.node() as SVGSVGElement, {
            update({ angle, frameAngle }: { angle: number, frameAngle: number }) {
                path.attr("transform", d => `translate(${d.origin}) rotate(${(angle / d.radius) % 360})`);
                frame.attr("transform", `rotate(${frameAngle % 360})`);
            }
        });
    }
    useEffect(() => {
        graphic();

        let id = 0;
        const animate = () => {
            const speed = 0.08;
            let angle = 0;
            let frameAngle = 0;

            let start = 0;
            const draw = function (timestamp: number) {
                if (start === 0) {
                    start = timestamp;
                }
                (d3.select(ref.current).node() as any).update({ angle, frameAngle });
                angle += speed;
                frameAngle += speed / 360;
                id = requestAnimationFrame(draw)

            }
            id = requestAnimationFrame(draw)
        }
        animate();
        return () => {
            cancelAnimationFrame(id);
        }

    })

    return (
        <svg ref={ref} >
        </svg>
    )
}

export default Gear;
```

</details>


## 数据可视化

### html + css

<div class="relative w-full border-solid border-lightgrey bg-skin-fill border-[1px]">
    <div class="w-[95%] bg-skin-accent h-3"/>
</div>

```html
<div class="relative w-full border-solid border-lightgrey bg-skin-fill border-[1px]">
    <div class="w-[95%] bg-skin-accent h-3"/>
</div>
```

### css-doodle



<Common  client:load>
@grid: 1 / 100% 100vmin / #0a0c27;
    background-size: 200px 200px;
    background-image: @doodle(
      @grid: 6 / 100%;
      @size: 4px;
      font-size: 4px;
      color: hsl(@r240, 30%, 50%);
      box-shadow: @m3x5(
        calc(4em - @nx * 1em) calc(@ny * 1em)
          @p(@m3(currentColor), @m2(transparent)),
        calc(2em + @nx * 1em) calc(@ny * 1em)
          @lp
      );
    );
</Common>

```tsx
import Common from '@components/react/doodle/common.tsx';

<Common  client:load>
@grid: 1 / 100vmin 100vmin / #0a0c27;
    background-size: 200px 200px;
    background-image: @doodle(
      @grid: 6 / 100%;
      @size: 4px;
      font-size: 4px;
      color: hsl(@r240, 30%, 50%);
      box-shadow: @m3x5(
        calc(4em - @nx * 1em) calc(@ny * 1em)
          @p(@m3(currentColor), @m2(transparent)),
        calc(2em + @nx * 1em) calc(@ny * 1em)
          @lp
      );
    );
</Common>
```

<Common client:load>
@grid: 1x1 / 100% 600px;
gap: 2px;
background: conic-gradient(
    at @r(30%, 70%) 0,
    @stripe(
      transparent 25%, 
      @m32.@p(
        #781217, #9A1320, #0A2667, #D8A088, #D6DDD1,
        #E95A22, #CA4122, #C74523, #2CACC9, #008C3B,
        #F5D700, #CA2821, #EADF93, #003A59, #00609A,
        #6E746E, #E2E0B8, #1E272B, #468495, #E1E0CA,
        #666F75, #84A16A, #413B4E, #98AEC7, #979DA4
      ),
      transparent 25%
    )
  );
</Common>


### webgl

- [webgl in regl](/mono_notes/posts/webgl)

### svg

- [svg basic](/mono_notes/posts/svg)


### d3基础

#### d3.arc() 弧

import PracticeArc from '@components/react/d3/practice-arc.tsx';

<PracticeArc client:load  / >

<details>
  <summary>practice-arc</summary>

```tsx
import * as d3 from "d3";
import { useEffect, useRef } from "react"

export default () => {
    const ref = useRef<SVGSVGElement | null>(null);
    useEffect(() => {
        const svgElement = d3.select(ref.current);
        const g = svgElement
            .attr("width", "100")
            .attr("height", "100")
            .append("path")

        g.attr("d", d3.arc()({
            innerRadius: 19,
            outerRadius: 40,
            startAngle: -Math.PI / 2,
            endAngle: Math.PI / 2
        }))
            .attr("fill", "cornflowerblue")
            .attr("style", "transform: translate(50%, 50%)"
            )
    })

    return (
        <svg ref={ref} >
        </svg>
    )
}


```
</details>

#### d3.axisBottom() 底部坐标轴

import PracticeAxisBottom from '@components/react/d3/practice-axis-bottom.tsx';

<PracticeAxisBottom client:load/>

<details>
  <summary>practice-axis-bottom</summary>
```tsx
import * as d3 from "d3";
import { useEffect, useRef } from "react"


const Template = () => {
    const ref = useRef<SVGSVGElement | null>(null);
    useEffect(() => {
        const xScale = d3.scaleLinear()
            .domain([0, 100])
            .range([10, 290])
        const svgElement = d3.select(ref.current).attr("style", "width:100%;max-height:4em");
        const axisGenerator = d3.axisBottom(xScale);
        svgElement.append("g")
            .call(axisGenerator)
        return () => {
            svgElement.remove();
        }
    })
    return (
        <svg ref={ref} >
        </svg>
    )
}
export default Template;

```
</details>




## 链接

- [d3.js官网](https://d3js.org/what-is-d3)
- [d3 in react](https://2019.wattenberger.com/blog/react-and-d3)
- [探索d3](https://observablehq.com/explore)
- [d3 api index](https://d3js.org/api)
- [webgl](https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API)